package ru.amse.tsyganov.jumleditor.storing;

import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import ru.amse.tsyganov.jumleditor.model.activity.ActionState;
import ru.amse.tsyganov.jumleditor.model.activity.Decision;
import ru.amse.tsyganov.jumleditor.model.activity.FinalState;
import ru.amse.tsyganov.jumleditor.model.activity.ForkJoinState;
import ru.amse.tsyganov.jumleditor.model.activity.InitialState;
import ru.amse.tsyganov.jumleditor.model.activity.Transition;
import ru.amse.tsyganov.jumleditor.model.activity.ActionState.CallActionMode;
import ru.amse.tsyganov.jumleditor.model.collaboration.Actor;
import ru.amse.tsyganov.jumleditor.model.collaboration.Instance;
import ru.amse.tsyganov.jumleditor.model.collaboration.Message;
import ru.amse.tsyganov.jumleditor.model.common.GraphModel;
import ru.amse.tsyganov.jumleditor.model.common.State;
import ru.amse.tsyganov.jumleditor.properties.PropertyChangeListener;
import ru.amse.tsyganov.jumleditor.view.ConnectionPoint;
import ru.amse.tsyganov.jumleditor.view.activity.ActionStateView;
import ru.amse.tsyganov.jumleditor.view.activity.DecisionView;
import ru.amse.tsyganov.jumleditor.view.activity.FinalStateView;
import ru.amse.tsyganov.jumleditor.view.activity.ForkJoinStateView;
import ru.amse.tsyganov.jumleditor.view.activity.InitialStateView;
import ru.amse.tsyganov.jumleditor.view.activity.TransitionView;
import ru.amse.tsyganov.jumleditor.view.collaboration.ActorView;
import ru.amse.tsyganov.jumleditor.view.collaboration.InstanceView;
import ru.amse.tsyganov.jumleditor.view.collaboration.MessageView;
import ru.amse.tsyganov.jumleditor.view.common.GraphView;
import ru.amse.tsyganov.jumleditor.view.common.StateView;

public class XmlReader {
	
	interface ItemHandler {
		
		void handleModelItem(Node node) throws StoreException;
		
		void handleViewItem(Node node);
	}
	
	private final Map<String, ItemHandler> handlers = new HashMap<String, ItemHandler>();
	
	private final Map<Integer, State> idToStateMap = 
		new HashMap<Integer, State>();
	
	private GraphModel graphModel;
	
	private GraphView<GraphModel> graphView;
	
	private PropertyChangeListener propertyChangeListener;

	public XmlReader() {
		ItemHandler actionStateHandler = new ItemHandler() {
			
			private final Map<Integer, ActionState> idToActionStateMap = 
				new HashMap<Integer, ActionState>();

			public void handleModelItem(Node node) {
				ActionState actionState = new ActionState();
								
				idToActionStateMap.put(getId(node),	actionState);
				idToStateMap.put(getId(node), actionState);
				
				CallActionMode mode = CallActionMode.valueOf(
						node.getAttributes().getNamedItem(
								TagNames.ACTION_STATE_CALL_ACTION_MODE).getNodeValue());
				
				actionState.setName(node.getAttributes().
						getNamedItem(TagNames.NAME).getNodeValue());
				
				actionState.setCallActionMode(mode);
			}

			public void handleViewItem(Node node) {
				ActionState actionState = idToActionStateMap.get(getId(node));
				ActionStateView<ActionState> actionStateView = 
					new ActionStateView<ActionState>(actionState, graphView);
								
				graphView.getModelViewMap().getStatesMap().put(actionState, actionStateView);
				graphModel.addState(actionState);
				initViewPosition(actionStateView, node);
				actionStateView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.ACTION_STATE, actionStateHandler);
		handlers.put(TagNames.ACTION_STATE_VIEW, actionStateHandler);
		
		ItemHandler actorHandler = new ItemHandler() {
			
			private final Map<Integer, Actor> idToActorMap = 
				new HashMap<Integer, Actor>();

			public void handleModelItem(Node node) {
				Actor actor = new Actor();
								
				idToActorMap.put(getId(node), actor);
				idToStateMap.put(getId(node), actor);
				
				actor.setName(node.getAttributes().
					getNamedItem(TagNames.NAME).getNodeValue());
				
				actor.setStereotype(node.getAttributes().
					getNamedItem(TagNames.ACTOR_STEREOTYPE).getNodeValue());
			}

			public void handleViewItem(Node node) {
				Actor actor = idToActorMap.get(getId(node));
				ActorView<Actor> actorView = 
					new ActorView<Actor>(actor, graphView);
								
				graphView.getModelViewMap().getStatesMap().put(actor, actorView);
				graphModel.addState(actor);
				initViewPosition(actorView, node);
				actorView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.ACTOR, actorHandler);
		handlers.put(TagNames.ACTOR_VIEW, actorHandler);
		
		ItemHandler instanceHandler = new ItemHandler() {
			
			private final Map<Integer, Instance> idToInstanceMap = 
				new HashMap<Integer, Instance>();

			public void handleModelItem(Node node) {
				Instance instance = new Instance();
								
				idToInstanceMap.put(getId(node), instance);
				idToStateMap.put(getId(node), instance);
				
				instance.setName(node.getAttributes().
						getNamedItem(TagNames.NAME).getNodeValue());
				instance.setRole(node.getAttributes().
						getNamedItem(TagNames.INSTANCE_ROLE).getNodeValue());
				instance.setType(node.getAttributes().
						getNamedItem(TagNames.INSTANCE_TYPE).getNodeValue());
				instance.setActive(Boolean.parseBoolean(node.getAttributes().
						getNamedItem(TagNames.INSTANCE_IS_ACTIVE).getNodeValue()));
				instance.setMultipleInstances(Boolean.parseBoolean(node.getAttributes().
						getNamedItem(TagNames.INSTANCE_IS_MULTIPLE_INSTANCES).getNodeValue()));
			}

			public void handleViewItem(Node node) {
				Instance instance = idToInstanceMap.get(getId(node));
				InstanceView<Instance> instanceView = 
					new InstanceView<Instance>(instance, graphView);
								
				graphView.getModelViewMap().getStatesMap().put(instance, instanceView);
				graphModel.addState(instance);
				initViewPosition(instanceView, node);
				instanceView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.INSTANCE, instanceHandler);
		handlers.put(TagNames.INSTANCE_VIEW, instanceHandler);
		
		ItemHandler decisionHandler = new ItemHandler() {
			
			private final Map<Integer, Decision> idToDecisionMap = 
				new HashMap<Integer, Decision>();

			public void handleModelItem(Node node) {
				Decision decision = new Decision();
								
				idToDecisionMap.put(getId(node), decision);
				idToStateMap.put(getId(node), decision);
				
				decision.setName(node.getAttributes().
						getNamedItem(TagNames.NAME).getNodeValue());
			}

			public void handleViewItem(Node node) {
				Decision decision = idToDecisionMap.get(getId(node));
				DecisionView<Decision> decisionView = 
					new DecisionView<Decision>(decision, graphView);
				
				graphView.getModelViewMap().getStatesMap().put(decision, decisionView);
				graphModel.addState(decision);
				initViewPosition(decisionView, node);
				decisionView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.DECISION, decisionHandler);
		handlers.put(TagNames.DECISION_VIEW, decisionHandler);
		
		ItemHandler finalStateHandler = new ItemHandler() {
			
			private final Map<Integer, FinalState> idToFinalStateMap = 
				new HashMap<Integer, FinalState>();

			public void handleModelItem(Node node) {
				FinalState finalState = new FinalState();
								
				idToFinalStateMap.put(getId(node),	finalState);
				idToStateMap.put(getId(node), finalState);
				
				finalState.setName(node.getAttributes().
						getNamedItem(TagNames.NAME).getNodeValue());
			}

			public void handleViewItem(Node node) {
				FinalState finalState = idToFinalStateMap.get(getId(node));
				FinalStateView<FinalState> finalStateView = 
					new FinalStateView<FinalState>(finalState, graphView);
			
				graphView.getModelViewMap().getStatesMap().put(finalState, finalStateView);
				graphModel.addState(finalState);
				initViewPosition(finalStateView, node);
				finalStateView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.FINAL_STATE, finalStateHandler);
		handlers.put(TagNames.FINAL_STATE_VIEW, finalStateHandler);
		
		ItemHandler forkJoinStateHandler = new ItemHandler() {
			
			private final Map<Integer, ForkJoinState> idToForkJoinStateMap = 
				new HashMap<Integer, ForkJoinState>();

			public void handleModelItem(Node node) {
				ForkJoinState forkJoinState = new ForkJoinState();
								
				idToForkJoinStateMap.put(getId(node), forkJoinState);
				idToStateMap.put(getId(node), forkJoinState);
				
				forkJoinState.setName(node.getAttributes().
						getNamedItem(TagNames.NAME).getNodeValue());
			}

			public void handleViewItem(Node node) {
				ForkJoinState forkJoinState = idToForkJoinStateMap.get(getId(node));
				ForkJoinStateView<ForkJoinState> forkJoinStateView = 
					new ForkJoinStateView<ForkJoinState>(forkJoinState, graphView);
								
				graphView.getModelViewMap().getStatesMap().put(forkJoinState, forkJoinStateView);
				graphModel.addState(forkJoinState);
				initViewPosition(forkJoinStateView, node);
				forkJoinStateView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.FORK_JOIN_STATE, forkJoinStateHandler);
		handlers.put(TagNames.FORK_JOIN_STATE_VIEW, forkJoinStateHandler);
		
		ItemHandler initialStateHandler = new ItemHandler() {
			
			private final Map<Integer, InitialState> idToInitialStateMap = 
				new HashMap<Integer, InitialState>();

			public void handleModelItem(Node node) {
				InitialState initialState = new InitialState();
								
				idToInitialStateMap.put(getId(node), initialState);
				idToStateMap.put(getId(node), initialState);
				
				initialState.setName(node.getAttributes().
						getNamedItem(TagNames.NAME).getNodeValue());
			}

			public void handleViewItem(Node node) {
				InitialState initialState = idToInitialStateMap.get(getId(node));
				InitialStateView<InitialState> initialStateView = 
					new InitialStateView<InitialState>(initialState, graphView);
				
				graphView.getModelViewMap().getStatesMap().put(initialState, initialStateView);
				graphModel.addState(initialState);
				initViewPosition(initialStateView, node);
				initialStateView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.INITIAL_STATE, initialStateHandler);
		handlers.put(TagNames.INITIAL_STATE_VIEW, initialStateHandler);
		
		ItemHandler messageHandler = new ItemHandler() {
			
			private final Map<Integer, Message> idToMessageMap = 
				new HashMap<Integer, Message>();

			public void handleModelItem(Node node) throws StoreException {
				
				Integer sourceId = Integer.parseInt(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_SOURCE_ID).
										getNodeValue());
				
				Integer targetId = Integer.parseInt(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_TARGET_ID).
										getNodeValue());
				
				if (!idToStateMap.containsKey(sourceId) || 
						!idToStateMap.containsKey(targetId)) {
					
					throw new StoreException("Error in xml references");
				}
				
				State sourceState = idToStateMap.get(sourceId);
				State targetState = idToStateMap.get(targetId);
				
				Message message = new Message(
						sourceState.getStateVertex(),
						targetState.getStateVertex());
				
				idToMessageMap.put(getId(node), message);
				
				message.setOperation(node.getAttributes().
						getNamedItem(TagNames.MESSAGE_OPERATION).getNodeValue());
				message.setArgument(node.getAttributes().
						getNamedItem(TagNames.MESSAGE_ARGUMENT).getNodeValue());
				message.setReturnValue(node.getAttributes().
						getNamedItem(TagNames.MESSAGE_RETURN_VALUE).getNodeValue());
				message.setSequenceNumber(node.getAttributes().
						getNamedItem(TagNames.MESSAGE_SEQUENCE_NUMBER).getNodeValue());
				message.setSynchronous(Boolean.parseBoolean(node.getAttributes().
						getNamedItem(TagNames.MESSAGE_SYNCHRONOUS).getNodeValue()));
				message.setCondition(node.getAttributes().
						getNamedItem(TagNames.MESSAGE_CONDITION).getNodeValue());
				message.setIteration(node.getAttributes().
						getNamedItem(TagNames.MESSAGE_ITERATION).getNodeValue());
			}

			public void handleViewItem(Node node) {
				Message message = idToMessageMap.get(getId(node));
				
				int sourceConnPointIndex = Integer.valueOf(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_SOURCE_CONN_POINT).getNodeValue());
				
				int targetConnPointIndex = Integer.valueOf(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_TARGET_CONN_POINT).getNodeValue());
				
				ConnectionPoint sourceConnPoint = 
					graphView.getModelViewMap().getStatesMap().get(
						message.getSource().getContainer()).getConnectionPoints().
							get(sourceConnPointIndex);
			
				ConnectionPoint targetConnPoint = 
					graphView.getModelViewMap().getStatesMap().get(
						message.getTarget().getContainer()).getConnectionPoints().
							get(targetConnPointIndex);
			
				MessageView<Message> messageView =  
					new MessageView<Message>(message, graphView,
						sourceConnPoint, targetConnPoint);
				
				graphView.getModelViewMap().getEdgesMap().put(message, messageView);
				
				int v = Integer.parseInt(node.getAttributes().
						getNamedItem(TagNames.EDGE_VERTICAL_SEGMENT).getNodeValue());
				
				int h = Integer.parseInt(node.getAttributes().
						getNamedItem(TagNames.EDGE_HORIZONTAL_SEGMENT).getNodeValue());
				
				messageView.initBrokenLine(v, h);
				
				message.addBindings();
				messageView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.MESSAGE, messageHandler);
		handlers.put(TagNames.MESSAGE_VIEW, messageHandler);
		
		ItemHandler transitionHandler = new ItemHandler() {
			
			private final Map<Integer, Transition> idToTransitionMap = 
				new HashMap<Integer, Transition>();

			public void handleModelItem(Node node) throws StoreException {
				
				Integer sourceId = Integer.parseInt(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_SOURCE_ID).
										getNodeValue());
				
				Integer targetId = Integer.parseInt(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_TARGET_ID).
										getNodeValue());
				
				if (!idToStateMap.containsKey(sourceId) || 
						!idToStateMap.containsKey(targetId)) {
					
					throw new StoreException("Error in xml references");
				}
				
				State sourceState = idToStateMap.get(sourceId);
				State targetState = idToStateMap.get(targetId);
				
				Transition transition = new Transition(
						sourceState.getStateVertex(),
						targetState.getStateVertex());
				
				idToTransitionMap.put(getId(node), transition);
				
				transition.setGuardExpression(node.getAttributes().
						getNamedItem(TagNames.GUARD_EXPRESSION).getNodeValue());
				transition.setGuard(Boolean.parseBoolean(node.getAttributes().
						getNamedItem(TagNames.GUARD).getNodeValue()));
			}

			public void handleViewItem(Node node) {
				Transition transition = idToTransitionMap.get(getId(node));
				
				int sourceConnPointIndex = Integer.valueOf(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_SOURCE_CONN_POINT).getNodeValue());
				
				int targetConnPointIndex = Integer.valueOf(
						node.getAttributes().getNamedItem(
								TagNames.EDGE_TARGET_CONN_POINT).getNodeValue());
				
				ConnectionPoint sourceConnPoint = 
					graphView.getModelViewMap().getStatesMap().get(
						transition.getSource().getContainer()).getConnectionPoints().
							get(sourceConnPointIndex);
			
				ConnectionPoint targetConnPoint = 
					graphView.getModelViewMap().getStatesMap().get(
						transition.getTarget().getContainer()).getConnectionPoints().
							get(targetConnPointIndex);
			
				TransitionView<Transition> transitionView =  
					new TransitionView<Transition>(transition, graphView,
						sourceConnPoint, targetConnPoint);
				
				graphView.getModelViewMap().getEdgesMap().put(transition, transitionView);
				
				int v = Integer.parseInt(node.getAttributes().
						getNamedItem(TagNames.EDGE_VERTICAL_SEGMENT).getNodeValue());
				
				int h = Integer.parseInt(node.getAttributes().
						getNamedItem(TagNames.EDGE_HORIZONTAL_SEGMENT).getNodeValue());
				
				transitionView.initBrokenLine(v, h);
				transition.addBindings();
				transitionView.addPropertyListener(propertyChangeListener);
			}
			
		};
		handlers.put(TagNames.TRANSITION, transitionHandler);
		handlers.put(TagNames.TRANSITION_VIEW, transitionHandler);
	}
	
	private static int getId(Node node) {
		return Integer.parseInt(node.getAttributes().getNamedItem(TagNames.ID).getNodeValue());
	}
	
	private static void initViewPosition(StateView<? extends State> stateView, Node node) {
		int x = Integer.parseInt(node.getAttributes().
				getNamedItem(TagNames.STATE_VIEW_X).getNodeValue());
		int y = Integer.parseInt(node.getAttributes().
				getNamedItem(TagNames.STATE_VIEW_Y).getNodeValue());
		int width = Integer.parseInt(node.getAttributes().
				getNamedItem(TagNames.STATE_VIEW_WIDTH).getNodeValue());
		int height = Integer.parseInt(node.getAttributes().
				getNamedItem(TagNames.STATE_VIEW_HEIGHT).getNodeValue());
		
		stateView.setX(x);
		stateView.setY(y);
		stateView.setWidth(width);
		stateView.setHeight(height);
	}
	
	public void readFromDocument(Document document, 
			GraphView<GraphModel> activityGraphView,
			PropertyChangeListener propertyChangeListener) 
		throws StoreException {
		
		this.graphModel = activityGraphView.getModel();
		this.graphView = activityGraphView;
		this.propertyChangeListener = propertyChangeListener;
		
		readGraphModel(document);
		
		readGraphView(document);
	}
	
	private void readGraphModel(Document document) throws StoreException {
		assert graphModel != null;
		
		Node activityGraphNode = document.getElementsByTagName(TagNames.GRAPH_MODEL).item(0);
		for (int i = 0; i < activityGraphNode.getChildNodes().getLength(); i++) {
			Node modelItemNode = activityGraphNode.getChildNodes().item(i);
			ItemHandler h = handlers.get(modelItemNode.getNodeName());
			h.handleModelItem(modelItemNode);
		}
	}
	
	private void readGraphView(Document document) {
		assert graphView != null;
		
		Node activityGraphViewNode = document.getElementsByTagName(TagNames.GRAPH_VIEW).item(0);
		for (int i = 0; i < activityGraphViewNode.getChildNodes().getLength(); i++) {
			Node viewItemNode = activityGraphViewNode.getChildNodes().item(i);
			ItemHandler h = handlers.get(viewItemNode.getNodeName());
			h.handleViewItem(viewItemNode);
		}
	}
}
